package ch.zhaw.skuh.turing.model.turingmaschine.rules;

import ch.zhaw.skuh.turing.model.turingmaschine.Addition_N_ary;
import ch.zhaw.skuh.turing.model.turingmaschine.Multiplication_N_ary;

public class MultiplicationRuleSet extends AdditionRuleSet {

	protected static int COUNTSTATES = Multiplication_N_ary.COUNTSTATES;

	public MultiplicationRuleSet(Addition_N_ary tc) {
		super(tc);
	}

	@Override
	public int setRules(int minState, int exitNormal, int exitSpecial,
			int[] tapes) {

		// *** multiplication rules ***

		int[] tapesCurrent;
		int diffState = 0;

		// *** addition rules *** q0-q1 (q1 is empty)

		// addition needs tapes 1, 2, 3
		tapesCurrent = new int[] { tapes[0], tapes[1], tapes[2] };

		// *** q0 rules *** addition rule : load modifed addition rule-set
		diffState = setAdditionRules(minState + 0, minState + 1, minState + 1,
				tapesCurrent);

		// *** multiplication rules *** continued

		// q1 of addition is empty
		diffState--;

		// multiplication needs tapes 1, 2, 4 5 (t3 is ignored when blank)
		tapesCurrent = new int[] { tapes[0], tapes[1], tapes[3], tapes[4] };

		// *** q1 rules *** run forwards, find end of t2
		addRule_findEndTape2(diffState + 0, diffState + 1, tapesCurrent);

		// *** q2 rules *** run backwards, multiply tape 3 and 4 to 1 and 2
		// continue with addition tape 1 and 2 --> addition-q1
		// start state !!
		addRule_multiply(diffState + 1, diffState + 2, exitNormal, tapesCurrent);

		// *** q3 rules *** prepare addition
		// move to end of tapes 1-4
		addRule_prepareAddition(diffState + 2, minState, tapesCurrent);

		// loaded rule-set, return new minState
		return minState + COUNTSTATES;
	}

	protected String getMultipliedValues(int iT2, int iT3, int iT4) {
		// new value
		int newVal = iT2 + (iT3 * iT4);
		// tape 1
		String t1 = "0";
		if (newVal >= tc.getRadix()) {
			t1 = Integer.toString(newVal / tc.getRadix(), tc.getRadix());
		}
		String t2 = Integer.toString(newVal % tc.getRadix(), tc.getRadix());
		String t3 = Integer.toString(iT3);
		String t4 = Integer.toString(iT4);
		return t1 + t2 + t3 + t4;
	}

	// find end of t2
	private void addRule_findEndTape2(int q, int exitNormal, int[] tapes) {
		int e = exitNormal;
		// t1, t3 + t4 are blank !!
		for (int iT2 = 0; iT2 < tc.getRadix(); iT2++) {
			String t2 = Integer.toString(iT2, tc.getRadix());
			tc.addRulePadded(q, "_" + t2 + "__", q, "_" + t2 + "__", "1100",
					tapes);
		}
		// all blank, write "0" to t1 as forward --> exit
		tc.addRulePadded(q, "____", e, "0___", "-1-1-1-1", tapes);
	}

	// multiply tape 3 and 4 to 1 and 2
	private void addRule_multiply(int q, int exitNormal, int endState,
			int[] tapes) {
		int e = exitNormal;

		String t4;

		// t1 is always blank

		// t4 is "0", shift t4 backwards, write 0 to t1
		for (int iT3 = 0; iT3 < tc.getRadix(); iT3++) {
			String t3 = Integer.toString(iT3, tc.getRadix());
			// t2 is blank
			// t4 is blank --> exit multiplication
			t4 = "_";
			tc.addRulePadded(q, "__" + t3 + t4, endState, "__" + t3 + t4,
					"1111", tapes);
			// t4 is "0" --> continue
			t4 = "0";
			tc.addRulePadded(q, "__" + t3 + t4, q, "0" + t4 + t3 + t4,
					"-1-10-1", tapes);
			for (int iT2 = 0; iT2 < tc.getRadix(); iT2++) {
				String t2 = Integer.toString(iT2, tc.getRadix());
				// t4 is "0" --> continue
				t4 = "0";
				tc.addRulePadded(q, "_" + t2 + t3 + t4, q, "0" + t2 + t3 + t4,
						"-1-10-1", tapes);
				// t4 is blank --> exit multiplication
				t4 = "_";
				tc.addRulePadded(q, "_" + t2 + t3 + t4, endState, "_" + t2 + t3
						+ t4, "1111", tapes);

			}
		}

		String write;

		// !!! iT4 > 0 !!!
		for (int iT4 = 1; iT4 < tc.getRadix(); iT4++) {
			t4 = Integer.toString(iT4, tc.getRadix());
			// t3 is blank --> exit
			tc.addRulePadded(q, "___" + t4, e, "___" + "0", "1111", tapes);
			for (int iT3 = 0; iT3 < tc.getRadix(); iT3++) {
				String t3 = Integer.toString(iT3, tc.getRadix());
				// t2 is blank --> continue
				write = getMultipliedValues(0, iT3, iT4);
				tc.addRulePadded(q, "__" + t3 + t4, q, write, "-1-1-10", tapes);
				for (int iT2 = 0; iT2 < tc.getRadix(); iT2++) {
					String t2 = Integer.toString(iT2, tc.getRadix());
					// all tapes filled --> continue
					write = getMultipliedValues(iT2, iT3, iT4);
					tc.addRulePadded(q, "_" + t2 + t3 + t4, q, write,
							"-1-1-10", tapes);
				}
			}
		}
	}

	// move to end of tapes 1-4
	private void addRule_prepareAddition(int q, int exitNormal, int[] tapes) {
		int e = exitNormal;
		// loop all 4 tapes, sub functions handle the rest except exit
		for (int iT1 = 0; iT1 <= tc.getRadix(); iT1++) {
			for (int iT2 = 0; iT2 <= tc.getRadix(); iT2++) {
				for (int iT3 = 0; iT3 <= tc.getRadix(); iT3++) {
					for (int iT4 = 0; iT4 <= tc.getRadix(); iT4++) {
						String moves = getPreAdditionMoves(new int[] { iT1,
								iT2, iT3, iT4 });
						String sTapes = getPreAdditionTapes(new int[] { iT1,
								iT2, iT3, iT4 });
						if (sTapes.compareTo("____") == 0) {
							// ends found, t1 was already forwarded (by
							// appending "0" in q0) --> exit to addition
							tc.addRulePadded(q, sTapes, e, "_" + "___",
									"-1-100", tapes);
						} 
						
						else if (sTapes.compareTo("0___") == 0) {
							// ends found, this is the "forwarded" 0 in t1
							tc.addRulePadded(q, sTapes, e, "0" + "___",
									"0-100", tapes);
						}
						
						else {
							// --> continue
							tc.addRulePadded(q, sTapes, q, sTapes, moves, tapes);
						}
					}
				}
			}
		}
	}

	private String getPreAdditionMoves(int[] iTapes) {
		StringBuilder sb = new StringBuilder(iTapes.length);
		int i = 0;
		for (; i < iTapes.length; i++) {
			if (iTapes[i] == tc.getRadix()) {
				sb.append('0');
			} else {
				sb.append('1');
			}
		}
		return sb.toString();
	}

	private String getPreAdditionTapes(int[] iTapes) {
		StringBuilder sb = new StringBuilder(iTapes.length);
		for (int i = 0; i < iTapes.length; i++) {
			if (iTapes[i] == tc.getRadix()) {
				sb.append('_');
			} else {
				String t = Integer.toString(iTapes[i], tc.getRadix());
				sb.append(t);
			}
		}
		return sb.toString();
	}

	@Override
	// override for addition
	protected void addRulesPart_Blank1_2(int q, int exitNormal,
			int exitSpecial, int[] tapes, String tOld, String tNew) {
		// todo t1 can have leading "0" (1 char longer then t2)
	}

	@Override
	protected void addRulesPart_Blank2_3(int q, int exitNormal,
			int exitSpecial, int[] tapes, String tOld, String tNew) {
		int e = exitNormal;
		// for addition after a forward of overrun (t1 only
		// 1 char longer)
		// avoid leading 0
		// --> exit
		if (tNew.compareTo("_0_") == 0) {
			tNew = "___";
		}
		tc.addRulePadded(q, tOld, e, tNew, "110", tapes);
	}

	@Override
	protected void addRulesPart_Blank2(int q, int exitNormal, int exitSpecial,
			int[] tapes, String tOld, String tNew) {
		int e = exitNormal;
		if (tNew.substring(2).compareTo("_") == 0) {
			// for addition after a forward of overrun (t1 only
			// 1 char longer)
			// avoid leading 0
			// --> exit
			tc.addRulePadded(q, tOld, e, tNew, "110", tapes);
		} else {
			// --> continue
			tc.addRulePadded(q, tOld, q, tNew, "-1-10", tapes);
		}
	}
}
